use anyhow::{bail, Result};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize};
use std::path::Path;
use thiserror::Error;
use toml;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(default)]
pub struct Page {
    pub title: Option<String>,
    #[serde(default, deserialize_with = "from_toml_datetime")]
    pub date: Option<String>,
    pub content: Option<String>,
}

impl Default for Page {
    fn default() -> Page {
        Page {
            title: None,
            date: None,
            content: None,
        }
    }
}

lazy_static! {
    static ref PAGE_RE: Regex =
        Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
}

#[derive(Error, Debug)]
pub enum MdErrors {
    #[error("没有发现文件:{file:?}")]
    FileUnfind { file: String },
    #[error("无法解析文件toml头部 {file} -> {err}")]
    Parse { file: String, err: String },
    #[error("未知错误")]
    Unknown,
}

fn split_content(file_path: &Path, content: &str) -> Result<(String, String)> {
    if !PAGE_RE.is_match(content) {
        // error::Error("song")
        bail!(MdErrors::FileUnfind {
            file: file_path.to_string_lossy().to_string(),
        })
    }
    let caps = PAGE_RE.captures(content).unwrap();

    Ok((caps[1].to_string(), caps[2].to_string()))
}

pub fn split_page_content(file_path: &Path, content: &str) -> Result<Page> {
    let (front_matter, content) = split_content(file_path, content)?;

    let mut page: Page = match toml::from_str(&front_matter) {
        Ok(p) => p,
        Err(e) => bail!(MdErrors::Parse {
            file: file_path.to_string_lossy().to_string(),
            err: e.to_string()
        }),
    };

    page.content = Some(content);

    Ok(page)
}

pub fn from_toml_datetime<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    toml::value::Datetime::deserialize(deserializer).map(|s| Some(s.to_string()))
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::split_page_content;

    #[test]
    fn can_split_page_content_valid() {
        let content = r#"
+++
title = "Title"
description = "hey there"
date = 2002-10-12a
+++
Hello
"#;
        let page = split_page_content(Path::new(""), content).unwrap();
        assert_eq!(page.content.unwrap(), "Hello\n");
        assert_eq!(page.title.unwrap(), "Title");
    }
}
